package com.ahua.mallchat.common.service.impl;

import com.ahua.mallchat.common.adapter.UserAdapter;
import com.ahua.mallchat.common.common.event.UserBlackEvent;
import com.ahua.mallchat.common.common.event.UserRegisterEvent;
import com.ahua.mallchat.common.dao.BlackDao;
import com.ahua.mallchat.common.dao.ItemConfigDao;
import com.ahua.mallchat.common.dao.UserBackpackDao;
import com.ahua.mallchat.common.dao.UserDao;
import com.ahua.mallchat.common.domain.dto.*;
import com.ahua.mallchat.common.domain.enums.user.BlackTypeEnum;
import com.ahua.mallchat.common.domain.enums.user.ItemEnum;
import com.ahua.mallchat.common.domain.enums.user.ItemTypeEnum;
import com.ahua.mallchat.common.domain.pojo.*;
import com.ahua.mallchat.common.domain.vo.user.BadgeResp;
import com.ahua.mallchat.common.domain.vo.user.ItemInfoVO;
import com.ahua.mallchat.common.domain.vo.user.SummeryInfoVO;
import com.ahua.mallchat.common.domain.vo.user.UserInfoResp;
import com.ahua.mallchat.common.service.UserService;
import com.ahua.mallchat.common.service.cache.ItemCache;
import com.ahua.mallchat.common.service.cache.UserCache;
import com.ahua.mallchat.common.service.cache.UserSummaryCache;
import com.ahua.mallchat.common.utils.AssertUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Description: 用户基础操作类
 */
@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Resource
    private UserDao userDao;

    @Resource
    private UserBackpackDao userBackpackDao;

    @Resource
    private ItemCache itemCache;
    @Resource
    private UserCache userCache;
    @Resource
    private ItemConfigDao itemConfigDao;
    @Resource
    private BlackDao blackDao;
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;
    @Autowired
    private UserSummaryCache userSummaryCache;

    @Override
    @Transactional
    public Long register(User user) {
        userDao.save(user);
        //发送用户注册事件
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, user));
        return user.getId();
    }


    @Override
    public UserInfoResp getUserInfo(Long uid) {
        User user = userDao.getById(uid);
        Integer modifyNameCount = userBackpackDao.getCountByItemId(uid, ItemEnum.MODIFY_NAME_CARD.getId());
        return UserAdapter.buildUserInfoResp(user, modifyNameCount);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void modifyName(Long uid, ModifyNameReq req) {
        //todo 判断名字中有没有敏感词
        //判断一下这个用户名是否存在
        User user = userDao.getByName(req.getName());
        AssertUtil.isEmpty(user, "该用户名已存在，请重新选名");
        //判断一下是否还有次数
        UserBackpack modifyNameItem = userBackpackDao.getFirstValidItem(uid, ItemEnum.MODIFY_NAME_CARD.getId());
        AssertUtil.isNotEmpty(modifyNameItem, "没有改名次数了噢");
        //使用改名卡
        boolean useSuccess = userBackpackDao.useItem(modifyNameItem);
        if (useSuccess) {//用乐观锁，就不用分布式锁了
            //改名
            userDao.modifyName(uid, req.getName());
            //删除缓存
            userCache.userInfoChange(uid);
        }
    }

    @Override
    public List<BadgeResp> badges(Long uid) {
        //1.获取徽章列表
        List<ItemConfig> itemConfigList = itemCache.getByType(ItemTypeEnum.BADGE.getType());
        //2.获取当前佩戴徽章
        User user = userDao.getById(uid);
        //3.获取当前用户拥有的徽章列表
        List<Long> itemConfigIdList = itemConfigList.stream().map(ItemConfig::getId).collect(Collectors.toList());
        List<UserBackpack> userObtainItemList = userBackpackDao.getUserObtainItemList(uid, itemConfigIdList);
        //4.返回
        return UserAdapter.buildBadgeResp(user, userObtainItemList, itemConfigList);
    }

    @Override
    public void wearingBadge(Long uid, WearingBadgeReq req) {
        //判断一下用户有没有这个物品
        UserBackpack userBackpack = userBackpackDao.getFirstValidItem(uid, req.getItemId());
        AssertUtil.isNotEmpty(userBackpack, "您暂时还没有这个徽章噢，无法佩戴");
        //确保这个物品是徽章
        ItemConfig itemConfig = itemConfigDao.getById(userBackpack.getItemId());
        AssertUtil.equal(itemConfig.getType(), ItemTypeEnum.BADGE.getType(), "只能佩戴徽章噢");
        //更新user佩戴的徽章
        userDao.wearingBadge(uid, req.getItemId());
        //删除用户缓存
        userCache.userInfoChange(uid);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void blackUser(BlackReq req) {
        //把这个uid存入黑名单表
        Long userId = req.getUserId();
        User user = userDao.getById(userId);
        AssertUtil.isNotEmpty(user, "该用户不存在");

        Black black = new Black();
        black.setTarget(userId.toString());
        black.setType(BlackTypeEnum.UID.getType());
        try {
            blackDao.save(black);
        } catch (Exception e) {
        }
        //针对这个用户的ip 我们也直接封掉
        blackIp(user);
        //发布一个拉黑事件 要发送消息通知给别的用户，还要把用户状态改掉
        applicationEventPublisher.publishEvent(new UserBlackEvent(this, user));
    }


    @Override
    public List<SummeryInfoVO> getSummeryUserInfo(SummeryInfoReq req) {
        //需要前端同步的uid
        List<Long> uidList = getNeedSyncUidList(req.getReqList());
        //加载用户信息
        Map<Long, SummeryInfoVO> batch = userSummaryCache.getBatch(uidList);
        return req.getReqList()
                .stream()
                .map(a -> batch.containsKey(a.getUid()) ? batch.get(a.getUid()) : SummeryInfoVO.skip(a.getUid()))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }

    @Override
    public List<ItemInfoVO> getItemInfo(ItemInfoReq req) {//简单做，更新时间可判断被修改
        return req.getReqList().stream().map(a -> {
            ItemConfig itemConfig = itemCache.getById(a.getItemId());
            if (Objects.nonNull(a.getLastModifyTime()) && a.getLastModifyTime() >= itemConfig.getUpdateTime().getTime()) {
                return ItemInfoVO.skip(a.getItemId());
            }
            ItemInfoVO dto = new ItemInfoVO();
            dto.setItemId(itemConfig.getId());
            dto.setImg(itemConfig.getImg());
            dto.setDescribe(itemConfig.getDescribe());
            return dto;
        }).collect(Collectors.toList());
    }

    private List<Long> getNeedSyncUidList(List<SummeryInfoReq.infoReq> reqList) {
        List<Long> needSyncUidList = new ArrayList<>();
        //获取用户的刷新时间 就是 前后端都要维护一个刷新时间 后端是存在redis中
        //这里用的是 mget 它的特点是 不存在的值会为null 比如key:1 2 3 values: value1 null value3
        //如果前端的刷新时间 小于后端的刷新时间 就说明要刷新了
        List<Long> userModifyTime = userCache.getUserModifyTime(reqList.stream().map(SummeryInfoReq.infoReq::getUid).collect(Collectors.toList()));
        for (int i = 0; i < reqList.size(); i++) {
            SummeryInfoReq.infoReq infoReq = reqList.get(i);
            Long modifyTime = userModifyTime.get(i);
            if (Objects.isNull(infoReq.getLastModifyTime()) || (Objects.nonNull(modifyTime) && modifyTime > infoReq.getLastModifyTime())) {
                needSyncUidList.add(infoReq.getUid());
            }
        }
        return needSyncUidList;
    }


    private void blackIp(User user) {
        String createIp = Optional.ofNullable(user.getIpInfo()).map(IpInfo::getCreateIp).orElse(null);
        String updateIp = Optional.ofNullable(user.getIpInfo()).map(IpInfo::getUpdateIp).orElse(null);

        saveBlackIp(createIp);
        saveBlackIp(updateIp);
    }

    private void saveBlackIp(String ip) {
        if (StringUtils.hasText(ip)) {
            try {
                Black black = new Black();
                black.setTarget(ip);
                black.setType(BlackTypeEnum.IP.getType());
                blackDao.save(black);
            } catch (Exception e) {
            }
        }
    }
}
